跳到主要内容

01 进阶操作系统

  • 什么是操作系统?

    • Windows,UNIX,Linux,Mac OS,Android,IOS,...
    • 操作系统是直接运行于硬件之上的计算机程序
    • 操作系统用于管理和控制计算机的硬件与软件资源
    • 操作系统为用户软件的开发提供必要的服务和接口
    • ......
  • 现代计算机系统架构

  • BIOS - Base Input&Output System

    • BIOS是计算机上电后第一个运行的程序
    • BIOS首先检测硬件状态,检测通过后立即进行硬件初始化
    • BIOS会在内存中建立中断向量表(提供硬件访问的方法)
    • BIOS最后将控制权交由主引导程序执行
  • 注意 BIOS不是软件(Software),而是固件(Firmware)! 固件是固化于硬件中的程序,在硬件出厂前已经烧写固定。

  • 系统启动流程(x86架构)

  • 问题 BIOS是如何被运行起来的?

  • BIOS的运行机制

    • BIOS存储于ROM中,地址映射为0xF0000-0xFFFFF(实地址)
    • BIOS的入口地址为:0xFFFF0
    • 硬件电路的特殊设计使得:开机后,CPU从0xFFFF0处开始执行
  • BIOS最后的使命

    • 按照用户设置扫描各个存储介质(光驱,软驱,U盘,等)
    • 发现主引导区后,将主引导中的主引导程序载入内存
    • 主引导程序在内存中的入口地址为0x7c00
    • 将控制权交由主引程序执行(jmp 0x7c00
  • 思考 BIOS如何在存储介质中寻找主引导区? 如何判断引导区中有没有主引导程序?

  • 主引导区(MBR:Master Boot Record)

    • 位置:位于存储介质的最开始位置处,大小为512字节
    • 特点:前512字节的最后2个有效字节为0x55aa
    • 数据:0x55aa之前的数据被视为主引导程序
  • 更详细的系统启动流程(x86架构)

小结

  • BIOS是计算机上电后第一个运行的程序
  • BIOS进行必要的初始化,并加载运行主导程序
  • 主引导程序位于存储介质的最开始512字节处
  • 主引导程序负责后续初始化,并加载运行操作系统内核

02 Hello,AmassOS!

  • 问题:主引导程序是软件还是固件?如果是软件,那么由谁开发?如何开发?

    软件,主引导程序并不是固化于硬件的。开发操作系统的人员负责开发主引导程序。

  • 主引导程序

    • 一段存储在主引导区(MBR)中的有效代码
    • 并不固化于硬件,属于操作系统代码的一部分
    • 启动操作系统内核的桥梁,由汇编程序写成
    • 代码总量不能超过512个字节(包含0x55aa)
  • 主引导程序的开发

    使用BIOS提供的中断向量表来使用中断服务程序的的功能。

  • 课程实验

    • 编写一个主引导程序(汇编语言)
    • 可独立运行于x86架构的主机(无操作系统)
    • 运行后在屏幕上打印"Hello,AmassOS!"
  • 实现思路

    1. 将关键寄存器的值设置为0,mov ax,0
    2. 定义需要打印的数据,db "Hello,AmassOS!"
    3. 打印预定义好的字符数据,int 0x10
  • 汇编小贴士

    • mov:赋值操作,将右操作数赋值给左操作数

      mov ax,0 ;将0赋值给ax寄存器
    • int:触发中断

      int 0x10 ;触发0x10中断,对屏幕进行操作
    • hlt:停止运行,CPU进入暂停状态,不执行任何操作

      hlt ;使程序进入睡眠状态
    • 汇编中地址的访问方式:段地址:段内偏移地址

      mov byte [0xb800:0x01],0x07 ;0xb800:0x01→0xb8000+0x01
    • 标签 用于标后续指令的地址(可等同为C语言中的标签)

      C语言使用goto时,需要定义标签。汇编语言中的数字要么是数据,要么是地址。

    • $vs$$ $表示当前指令行地址,$$表示当前汇编段起始地址

  • 中断调用VS函数调用

    在屏幕上打印一个字符

编程实验:第一个引导加载程序

此处并没有进行分段,也就是数据、代码都是集中放在一起的。在最开始执行的时候,cs代码段寄存器的值也为0,所以可以直接用它来初始化各个段寄存器的值

org 0x7c00 ;org指明入口地址

start: ;定义一个标签,同时指示了下面一行代码的地址值
mov ax,cs
mov ss,ax
mov ds,ax
mov es,ax

mov si,msg

print:
mov al,[si]
add si,1
cmp al,0x00
je last
mov ah,0x0e
mov bx,0x0f
int 0x10
jmp print

last:
hlt
jmp last

msg:
db 0x0a,0x0a
db "Hello,AmassOS!"
db 0x0a,0x0a
times 510-($-$$) db 0x00
db 0x55,0xaa
  • 问题:如何验证编写的主引导程序?
  • 解决方案设计
    • 将汇编源码编译为二进制机器码(nasm)
    • 创建虚拟盘(bximage)
    • 将二进制代码写入虚拟盘起始位置(dd)
    • 在虚拟机中将虚拟盘作为启动盘执行(vmware)

实验原材料

  • nasmnasm boot.asm -o boot.bin

  • bximagebximage -mode=create -fd="1.44M" -q a.img

    回车后输入1表明创建新的软盘,然后一直回车即可生成a.img

  • dddd if=boot.bin of=a.img bs=512 count=1 conv=notrunc

  • 运行引导加载程序

    1. 在 VMware中以自定义方式创建虚拟机。
    2. 选择稍后安装操作系统。
    3. 客户机操作系统选择其他选项,版本同样选择其他选项。
    4. 虚拟机名称设为AmassOS
    5. 勾选不使用网络连接
    6. 其他所有创建步骤中的选项使用默认选项即可。
    7. 此时创建虚拟机完成,运行虚拟机,发现屏幕会打印Operating System not found
    8. 现在关闭客户机,点击编辑虚拟机设置,在硬件选项卡中选择软盘选项,然后在右侧面板勾选使用软盘映像文件,然后点击浏览选中a.img文件。(如果在硬件选项卡没有发现软盘选项,点击底部的添加按钮,找到软盘选项进行添加即可)。
    9. 运行虚拟机,能够发现屏幕打印出Hello,AmassOS!

小结

  • 主引导程序的代码量不能超过512字节
  • 主引导程序需要使用汇编语言开发
  • 主引导程序中可以通过BIOS中断使用硬件功能
  • 主引导程序运行于实模式(地址都是实际的物理地址)

03 调试环境的搭建

  • 问题:如何调试主引导区的代码?

  • Bochs(另一款优秀的虚拟机软件)

    • 专业模拟x86架构的虚拟机
    • 开源且高度可移植,由C++编写完成
    • 支持操作系统开发过程中的断点调试
    • 通过简单配置就能够运行大多数主流的操作系统
  • 支持调试功能的Bochs版本,sudo apt install bochs bochs-x ,或者通过源码编译安装。

    tar -xzvf bochs-2.6.9.tar.gz
    cd bochs-2.6.9/
    ./configure --enable-debugger --enable-disasm --prefix=/opt/bochs-2.6.9
    make -j8
    make install

    sudo vim /etc/profile
    #Add "export PATH=$PATH:/opt/bochs-2.6.9/bin" to /etc/profile
  • 小贴士

    • 确定bochs的安装路径(which bochs

      需要确定BIOS-bochs-latest文件路径,通过源码安装的一般为安装目录下的${INSTALL_PREFIX}/share/bochs/BIOS-bochs-latest,通过apt-get安装的一般为/usr/share/bochs/BIOS-bochs-latest

      以及x11-pc-us.map文件所在的文件路径。

    • 安装vgabios(sudo apt-get install vgabios

    • 确定vgabios的安装路径(whereis vgabios

  • Bochs的启动文件(修改为正确的路径后,在工程目录下保存为bochsrc文件)

    ###############################################################
    # Configuration file for Bochs
    ###############################################################

    # how much memory the emulated machine will have
    megs: 32

    # filename of ROM images
    romimage: file=/usr/share/bochs/BIOS-bochs-latest
    vgaromimage: file=/usr/share/vgabios/vgabios.bin

    # what disk images will be used
    floppya: 1_44=a.img, status=inserted

    # choose the boot disk.
    boot: floppy

    # where do we send log messages?
    # log: bochsout.txt

    # disable the mouse
    mouse: enabled=0

    # enable key mapping, using US layout as default.
    keyboard: type=mf, keymap=/usr/share/bochs/keymaps/x11-pc-us.map
  • 启动bochs虚拟机

    • 显示方式:bochs -f bochsrc_file
    • 隐式方式:bochsbochs会按照.bochsrcbochsrcbochsrc.txt的顺序查找当前目录下,是否有这些启动文件中的一个。)
    • 运行bochs后,bochs会在启动最开始处中断,输入continue以继续运行。(说明bochs已支持断点调试)
  • 调试环境验证

Bochs中的常用调试命令

不难发现,与GDB命令几乎一样

命令功能示例
b(break)设置断点b 0x7c00
c(continue)继续执行c
s(step)单步执行s
info b(info break)查看当前所有断点info b
info cpu查看当前CPU状态info cpu
r(reg)查看常规寄存器状态r
sreg查看段寄存器状态sreg
x /Nuf epression查看内存中的数据x /2bx 0x7c00
trace on/off/开关:打印执行的指令trace on
trace-reg on/off/开关:打印寄存器的值trace-reg on
我们可以在运行bochs之后,当bochs在最开始处中断时,我们可以输入break 0x7c00进行打断点,0x7c00是主引导程序的入口地址。可以使用info break来查看是否有以下输入来验证断点是否打入成功
<bochs:3> info break
Num Type Disp Enb Address
1 pbreakpoint keep y 0x000000007c00

然后我们可以键入continuebochs继续运行,然后能够发现它停在了0x7c00处,终端会打印如下输出

00014479588i[BIOS  ] Booting from 0000:7c00
(0) Breakpoint 1, 0x0000000000007c00 in ?? ()
Next at t=14479643
(0) [0x000000007c00] 0000:7c00 (unk. ctxt): mov ax, cs ; 8cc8

0000:7c00是使用的[段地址:段内偏移地址]方法表示的地址,mov ax, cs 表示下一条将要执行的指令,8cc8为该行的汇编代码编译得出的机器码。

输入reg可以查看各个寄存器的值,可以看到指令寄存器ip被初始化为0x7c00,即上面mov ax, cs所在的地址。

<bochs:5> reg
CPU0:
rax: 00000000_0000aa55
rbx: 00000000_00000000
rcx: 00000000_00090000
rdx: 00000000_00000000
rsp: 00000000_0000ffd6
rbp: 00000000_00000000
rsi: 00000000_000e0000
rdi: 00000000_0000ffac
r8 : 00000000_00000000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: 00000000_00007c00
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf

也可以输入sreg查看各个段寄存器的值,以验证在编写boot.asm时,用cs代码段寄存器初始化其他各个段寄存器的值是否正缺。

<bochs:6> sreg
es:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
cs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ss:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ds:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
fs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
gs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1
gdtr:base=0x00000000000f9af7, limit=0x30
idtr:base=0x0000000000000000, limit=0x3ff

可以输入stepbochs只执行一条指令。

<bochs:7> step
Next at t=14479644
(0) [0x000000007c02] 0000:7c02 (unk. ctxt): mov ss, ax ; 8ed0

可以说输入x /2bx 0x7c00,查看0x7c00地址处2个byte字节大小的数据, 然后以十六进制打印,为0x8cc8,也即是boot.asm第一行汇编代码的机器指令。

<bochs:8> x /2bx 0x7c00
[bochs]:
0x0000000000007c00 <bogus+ 0>: 0x8c 0xc8

输入trace on,可以让bochs打印当前执行完的指令。

<bochs:9> trace on
Tracing enabled for CPU0
<bochs:10> step
(0).[14479644] [0x000000007c02] 0000:7c02 (unk. ctxt): mov ss, ax ; 8ed0
Next at t=14479645
(0) [0x000000007c04] 0000:7c04 (unk. ctxt): mov ds, ax ; 8ed8

我们输入step后,此处打印多出来一行mov ss, ax 为当前执行完的指令,下面的mov ds, ax 为下一条将要执行的指令,继续输入step

<bochs:11> step
(0).[14479645] [0x000000007c04] 0000:7c04 (unk. ctxt): mov ds, ax ; 8ed8
Next at t=14479646
(0) [0x000000007c06] 0000:7c06 (unk. ctxt): mov es, ax ; 8ec0

输入trace-reg on可以查看寄存器的前后状态,如下面的输出,bochs最先输出执行当前执行之前的寄存器状态,然后执行完当前执行后,就会输出执行完当前指令的寄存器状态以及下一条将要执行的指令。

<bochs:14> trace-reg on
Register-Tracing enabled for CPU0
<bochs:15> step
CPU0:
rax: 00000000_00000000
rbx: 00000000_00000000
rcx: 00000000_00090000
rdx: 00000000_00000000
rsp: 00000000_0000ffd6
rbp: 00000000_00000000
rsi: 00000000_000e0000
rdi: 00000000_0000ffac
r8 : 00000000_00000000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: 00000000_00007c06
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0).[14479646] [0x000000007c06] 0000:7c06 (unk. ctxt): mov es, ax ; 8ec0
Next at t=14479647
CPU0:
rax: 00000000_00000000
rbx: 00000000_00000000
rcx: 00000000_00090000
rdx: 00000000_00000000
rsp: 00000000_0000ffd6
rbp: 00000000_00000000
rsi: 00000000_000e0000
rdi: 00000000_0000ffac
r8 : 00000000_00000000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: 00000000_00007c08
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x000000007c08] 0000:7c08 (unk. ctxt): mov si, 0x7c20 ; be207c

小结

  • Bochs是一款专业模拟x86架构的虚拟机
  • Bochs可以获得调试功能的支持
  • Bochs的启动配置文件是正确运行关键
  • Bochs支持断点调试,其调试命令与GDB类似